#!/usr/bin/env python
# encoding: utf-8
"""
DRIDE.py

DRIDE is a dimensionality reduction and image database exploration tool
designed for global exploration of large image databases.

For usage information, please type

  % ./DRIDE.py --help

in a console.

Created by Alakazam on 2008-05-26.
Copyright (c) 2008. All rights reserved.
"""

# Global variables
_version     = "0.2"
_name        = "DRIDE"
_shortDesc   = "a dimensionality reduction and image database exploration toolkit"
_longVersion = _name + " " + _version + ", " + _shortDesc
_longDesc    = _name + " is " + _shortDesc + " that aims at aiding in exploring large image databases."

# Import statements
from Log            import Log
from Generator      import Generator
from Reducer        import Reducer
from OpenGLDataView import OpenGLDataView

from inspect  import getargspec
from optparse import OptionParser, OptionGroup

# Use psyco for jit compilation
import psyco
if __name__ == '__main__':
  psyco.log()
psyco.full()

class DRIDE(object):
  def __init__(self,
      generator="SwissRoll", n=500, param=1, noise=0, dim=3,
      reducer="PCA", k=5, outputDim=3,
      verbose=False):
    """
    Initialize options
    ==================

    generator: the generator to use, see Generator.generators for a list of valid values
    ==========

    n:     number of points of generated data
    param: parameter to pass to the generator
    noise: amount of noise to add to the input data
    dim:   dimensionality of the input data

    reducer:
    ========

    k:         number of neighbouring points to consider for nearest neighbour algorithms
    outputDim: target dimensionality for the reduction algorithm

    additional parameters
    =====================

    verbose: whether to display additional information wrt to execution
    """
    
    # Logging
    self.log = Log(verbose)

    self.log("Logging enabled...")

    # Generate data
    self.n         = n

    self.log.start("Generating data: %s"%generator)

    self.generator = Generator(generator=generator, n=n, param=param, noise=noise, dim=dim)
    self.generator.generateData()

    self.log.stop()

    # Reduction options
    self.reducer     = Reducer(self.generator.data, algorithm=reducer, k=k, outDim=outputDim, log=self.log)
  
  def displayOutput(self):
    """Display the results"""
    
    self.log.start("Opening OpenGLDataView")
    
    openGLView = OpenGLDataView(self.reducer.outData, neighbours=self.reducer.knn, colors=self.generator.colors)
    
    self.log.stop()
    self.log("Running OpenGLDataView...")
    
    openGLView.run()
  
  def run(self):
    self.reducer.doReduction()
    self.displayOutput()
  

if __name__ == '__main__':
  parser = OptionParser(
    prog=_name, usage="%prog [-g GENERATOR] [-r REDUCER]",
    version=_longVersion, description=_longDesc)
  
  parser.add_option(
    "-v", "--verbose", action="store_true",
    help="Enable verbose output (calculation durations, etc.)")
  
  group  = OptionGroup(parser, "Input data options")
  
  group.add_option(
    "-g", "--generator", type="choice", choices=Generator.generators,
    help="Generator to use for input data [default: %%default]. "
         "The following generators are available: %(generators)s"%{
           'generators': Generator.generators})
  group.add_option(
    "-n", "--points", type="int", dest="n",
    help="Number of input data points [default: %default]")
  group.add_option(
    "-p", "--generator-param", type="float", dest="param",
    help="Value of the additional parameter for the generator [default: %default].")
  group.add_option(
    "-b", "--noise", type="int",
    help="Amount of noise in the input data [default: %default]")
  group.add_option(
    "-d", "--dim", type="int",
    help="Input data dimensionality [default: %default]")
  
  parser.add_option_group(group)
  
  group  = OptionGroup(parser, "Output options")
  
  group.add_option(
    "-r", "--reducer", type="choice", choices=Reducer.algorithms,
    help="Reduction algorithm to use [default: %%default]. "
         "The following algorithms are available: %(algorithms)s"%{
         'algorithms': Reducer.algorithms})
  group.add_option(
    "-k", "--neighbours", dest="k", type="int",
    help="Number of neighbours to consider [default: %default]")
  group.add_option(
    "-o", "--output-dim", type="int",
    help="Dimensionality of output data [default: %default]")
  
  parser.add_option_group(group)
  
  # Get defaults from the DRIDE.__init__ method
  spec = getargspec(DRIDE.__init__)
  defaults = dict(zip(spec[0][-len(spec[3]):], spec[3]))
  
  parser.set_defaults(
    generator=defaults['generator'],
    n=defaults['n'], dim=defaults['dim'],
    param=defaults['param'], noise=defaults['noise'],
    reducer=defaults['reducer'], output_dim=defaults['outputDim'],
    k=defaults['k'],
    verbose=defaults['verbose'])
  
  (options, args) = parser.parse_args()
  
  parser.destroy()
  
  DRIDE(
    generator=options.generator,
    n=options.n, param=options.param, noise=options.noise, dim=options.dim,
    reducer=options.reducer, k=options.k, outputDim=options.output_dim,
    verbose=options.verbose
      ).run()
  
